#include "lua_module_http.h"
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
#include <string.h>
#include <stdlib.h>
#include "cm_os.h"
#include "app_log.h"
#include "lua_test_entry.h"

#define LUA_HTTP_CLIENT_METATABLE "http_client_metatable"

// Simple string builder utility
typedef struct {
    char *buffer;
    size_t capacity;
    size_t length;
} lua_str_builder_t;

static void lua_str_builder_init(lua_str_builder_t *builder) {
    builder->buffer = (char*)malloc(256); // Initial size
    builder->capacity = builder->buffer ? 256 : 0;
    builder->length = 0;
    if (builder->buffer) {
        builder->buffer[0] = '\0';
    }
}

static void lua_str_builder_append_str(lua_str_builder_t *builder, const char *str) {
    if (!builder->buffer || !str) return;
    size_t str_len = strlen(str);
    if (builder->length + str_len >= builder->capacity) {
        size_t new_capacity = builder->capacity * 2;
        while (new_capacity <= builder->length + str_len) {
            new_capacity *= 2;
        }
        char *new_buffer = (char*)realloc(builder->buffer, new_capacity);
        if (!new_buffer) return; // Realloc failed
        builder->buffer = new_buffer;
        builder->capacity = new_capacity;
    }
    strcat(builder->buffer, str);
    builder->length += str_len;
}

static char* lua_str_builder_to_string(lua_str_builder_t *builder) {
    if (!builder) return NULL;
    return builder->buffer; // Transfer ownership
}


// Map SDK handles to our Lua client objects
static lua_http_client_t* g_client_map[HTTPCLIENT_CTX_MAX_NUM] = {NULL};

// Forward declaration of the C callback
static void http_event_callback(cm_httpclient_handle_t client_handle, cm_httpclient_callback_event_e event, void *param);

void cleanup_client_resources(lua_http_client_t *client)
{
    if (client->handle)
    {
        for (int i = 0; i < HTTPCLIENT_CTX_MAX_NUM; ++i) {
            if (g_client_map[i] == client) {
                g_client_map[i] = NULL;
                break;
            }
        }
        cm_httpclient_terminate(client->handle); // Forcefully terminate connection
        cm_httpclient_delete(client->handle);
        client->handle = NULL;
    }
    client->state = HTTP_STATE_DONE;
    luaL_unref(client->L, LUA_REGISTRYINDEX, client->on_done_ref);
    luaL_unref(client->L, LUA_REGISTRYINDEX, client->on_error_ref);
    luaL_unref(client->L, LUA_REGISTRYINDEX, client->on_progress_ref);
    luaL_unref(client->L, LUA_REGISTRYINDEX, client->self_ref); // Allow GC
    client->on_done_ref = LUA_NOREF;
    client->on_error_ref = LUA_NOREF;
    client->on_progress_ref = LUA_NOREF;
    client->self_ref = LUA_NOREF;

    if (client->response_buffer) {
        free(client->response_buffer);
        client->response_buffer = NULL;
        client->buffer_size = 0;
        client->total_len = 0;
    }

    if (client->file_handle) {
        cm_fs_close(client->file_handle);
        client->file_handle = 0;
    }

    if (client->request_body_copy) {
        free(client->request_body_copy);
        client->request_body_copy = NULL;
    }

    if (client->request_header_copy) {
        free(client->request_header_copy);
        client->request_header_copy = NULL;
    }
}

// Lua: client:on(eventName, callback)
static int l_client_on(lua_State *L) {
    lua_http_client_t *client = (lua_http_client_t *)luaL_checkudata(L, 1, LUA_HTTP_CLIENT_METATABLE);
    const char *event_name = luaL_checkstring(L, 2);
    luaL_checktype(L, 3, LUA_TFUNCTION);

    if (strcmp(event_name, "done") == 0) {
        luaL_unref(L, LUA_REGISTRYINDEX, client->on_done_ref);
        client->on_done_ref = luaL_ref(L, LUA_REGISTRYINDEX);
    } else if (strcmp(event_name, "error") == 0) {
        luaL_unref(L, LUA_REGISTRYINDEX, client->on_error_ref);
        client->on_error_ref = luaL_ref(L, LUA_REGISTRYINDEX);
    } else if (strcmp(event_name, "progress") == 0) {
        luaL_unref(L, LUA_REGISTRYINDEX, client->on_progress_ref);
        client->on_progress_ref = luaL_ref(L, LUA_REGISTRYINDEX);
    } else {
        return luaL_argerror(L, 2, "unsupported event name, use 'done', 'error', or 'progress'");
    }
    return 0;
}

static int start_async_request(lua_State *L, int is_download) {
    lua_http_client_t *client = (lua_http_client_t *)luaL_checkudata(L, 1, LUA_HTTP_CLIENT_METATABLE);
    if (client->state != HTTP_STATE_IDLE) {
        return luaL_error(L, "http client is busy");
    }

    const char *method_str = luaL_checkstring(L, 2);
    const char *url = luaL_checkstring(L, 3);
    const char *filepath = NULL;
    int headers_idx = 4;
    int body_idx = 5;

    if (is_download) {
        filepath = luaL_checkstring(L, 4);
        headers_idx = 5;
        body_idx = 6;
    }

    if (is_download) {
        client->file_handle = cm_fs_open(filepath, CM_FS_WB);
        if (client->file_handle <= 0) {
            return luaL_error(L, "failed to open file for writing: %s", filepath);
        }
    }

    // If a handle was pre-created by set_timeout, delete it first.
    // The real handle will be created with the correct URL.
    if (client->handle) {
        // We don't need to unmap it from g_client_map because it wasn't mapped
        cm_httpclient_delete(client->handle);
        client->handle = NULL;
    }

    // Parse URL
    char *host = NULL;
    const char *path = NULL;
    const char *path_start = strstr(url, "://");
    if (path_start) {
        path_start += 3;
        path = strchr(path_start, '/');
    } else {
        path = strchr(url, '/');
    }

    if (path) {
        int host_len = path - url;
        host = malloc(host_len + 1);
        if (!host) { cleanup_client_resources(client); return luaL_error(L, "malloc failed for host"); }
        memcpy(host, url, host_len);
        host[host_len] = '\0';
    } else {
        host = strdup(url);
        path = "/";
    }

    cm_httpclient_ret_code_e ret = cm_httpclient_create((uint8_t*)host, http_event_callback, &client->handle);
    free(host);
    if (ret != CM_HTTP_RET_CODE_OK) {
        cleanup_client_resources(client);
        return luaL_error(L, "cm_httpclient_create failed: %d", ret);
    }

    int i;
    for (i = 0; i < HTTPCLIENT_CTX_MAX_NUM; ++i) {
        if (g_client_map[i] == NULL) {
            g_client_map[i] = client;
            break;
        }
    }
    if (i == HTTPCLIENT_CTX_MAX_NUM) {
        cleanup_client_resources(client);
        return luaL_error(L, "no free http client slots");
    }

    lua_pushvalue(L, 1);
    client->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);

    // Set headers if provided
    if (lua_istable(L, headers_idx)) {
        lua_pushnil(L); // first key
        lua_str_builder_t h_builder;
        lua_str_builder_init(&h_builder);
        while (lua_next(L, headers_idx) != 0) {
            const char* key = lua_tostring(L, -2);
            const char* value = lua_tostring(L, -1);
            lua_str_builder_append_str(&h_builder, key);
            lua_str_builder_append_str(&h_builder, ": ");
            lua_str_builder_append_str(&h_builder, value);
            lua_str_builder_append_str(&h_builder, "\r\n");
            lua_pop(L, 1);
        }
        client->request_header_copy = lua_str_builder_to_string(&h_builder);
        if (client->request_header_copy) {
            cm_httpclient_custom_header_set(client->handle, (uint8_t*)client->request_header_copy, strlen(client->request_header_copy));
        }
    }

    cm_httpclient_request_type_e method;
    if (strcmp(method_str, "GET") == 0) method = HTTPCLIENT_REQUEST_GET;
    else if (strcmp(method_str, "POST") == 0) method = HTTPCLIENT_REQUEST_POST;
    else {
        cleanup_client_resources(client);
        return luaL_argerror(L, 2, "unsupported http method");
    }

    size_t body_len = 0;
    const char *body_str = NULL;
    if (!lua_isnoneornil(L, body_idx)) {
        body_str = luaL_checklstring(L, body_idx, &body_len);
        if (body_len > 0) {
            client->request_body_copy = (char *)malloc(body_len);
            if (!client->request_body_copy) {
                cleanup_client_resources(client);
                return luaL_error(L, "malloc failed for request body");
            }
            memcpy(client->request_body_copy, body_str, body_len);
        }
    }

    ret = cm_httpclient_request_start(client->handle, method, (const uint8_t*)path, false, body_len);
    if (ret != CM_HTTP_RET_CODE_OK) {
        cleanup_client_resources(client);
        return luaL_error(L, "cm_httpclient_request_start failed: %d", ret);
    }

    // Body will be sent in the START_SUCC callback
    // cm_httpclient_request_end(client->handle); // End will also be called in callback

    client->state = HTTP_STATE_STARTED;
    lua_pushboolean(L, 1);
    return 1;
}

// Lua: client:request(...)
static int l_client_request(lua_State *L) {
    return start_async_request(L, 0); // Not a download
}

// Lua: client:download(...)
static int l_client_download(lua_State *L) {
    return start_async_request(L, 1); // Is a download
}

// Garbage Collection
static int l_client_gc(lua_State *L) {
    lua_http_client_t *client = (lua_http_client_t *)luaL_checkudata(L, 1, LUA_HTTP_CLIENT_METATABLE);
    cleanup_client_resources(client);
    return 0;
}

// tostring
static int l_client_tostring(lua_State *L)
{
    lua_http_client_t *client = (lua_http_client_t *)luaL_checkudata(L, 1, LUA_HTTP_CLIENT_METATABLE);
    lua_pushfstring(L, "http_client(%p)", client);
    return 1;
}


// Lua: client = http.create()
static int l_http_create(lua_State *L)
{
    lua_http_client_t *client = (lua_http_client_t *)lua_newuserdata(L, sizeof(lua_http_client_t));
    memset(client, 0, sizeof(lua_http_client_t));
    client->L = L;
    client->state = HTTP_STATE_IDLE;
    client->on_done_ref = LUA_NOREF;
    client->on_error_ref = LUA_NOREF;
    client->on_progress_ref = LUA_NOREF;
    client->self_ref = LUA_NOREF;
    client->request_body_copy = NULL;
    client->request_header_copy = NULL;
    luaL_setmetatable(L, LUA_HTTP_CLIENT_METATABLE);
    return 1;
}

// Lua: ok = client:set_timeout(connect_ms, response_ms)
static int l_client_set_timeout(lua_State *L) {
    lua_http_client_t *client = (lua_http_client_t *)luaL_checkudata(L, 1, LUA_HTTP_CLIENT_METATABLE);
    if (client->state != HTTP_STATE_IDLE) {
        return luaL_error(L, "set_timeout must be called before request/download");
    }

    // This function must be called *after* cm_httpclient_create,
    // but the handle is not created until the request starts.
    // So we need to create the handle here if it doesn't exist.
    // This is a bit awkward due to the SDK design, we will use a dummy URL for now.
    // The real URL will be set during the actual request.
    if (!client->handle) {
        // A dummy URL to create the handle, it will be replaced later.
        const char* dummy_url = "http://127.0.0.1";
        cm_httpclient_ret_code_e ret = cm_httpclient_create((uint8_t*)dummy_url, http_event_callback, &client->handle);
        if (ret != CM_HTTP_RET_CODE_OK) {
             return luaL_error(L, "failed to pre-create http client for config: %d", ret);
        }
    }

    int connect_timeout_s = luaL_checkinteger(L, 2) / 1000; // SDK expects seconds
    int response_timeout_s = luaL_checkinteger(L, 3) / 1000; // SDK expects seconds

    cm_httpclient_cfg_t cfg = {0};
    cfg.conn_timeout = connect_timeout_s;
    cfg.rsp_timeout = response_timeout_s;

    cm_httpclient_ret_code_e ret = cm_httpclient_set_cfg(client->handle, cfg);

    lua_pushboolean(L, ret == CM_HTTP_RET_CODE_OK);
    return 1;
}

static const luaL_Reg http_lib[] = {
    {"create", l_http_create},
    {NULL, NULL}
};

static const luaL_Reg http_client_methods[] = {
    {"on", l_client_on},
    {"request", l_client_request},
    {"download", l_client_download},
    {"set_timeout", l_client_set_timeout},
    {"__gc", l_client_gc},
    {"__tostring", l_client_tostring},
    {NULL, NULL}
};

int luaopen_http(lua_State *L)
{
    luaL_newmetatable(L, LUA_HTTP_CLIENT_METATABLE);
    lua_pushvalue(L, -1);
    lua_setfield(L, -2, "__index");
    luaL_setfuncs(L, http_client_methods, 0);
    lua_pop(L, 1);

    luaL_newlib(L, http_lib);
    return 1;
}

// C callback function
static void http_event_callback(cm_httpclient_handle_t client_handle, cm_httpclient_callback_event_e event, void *param)
{
    lua_http_client_t *client = NULL;
    for (int i = 0; i < HTTPCLIENT_CTX_MAX_NUM; ++i) {
        if (g_client_map[i] && g_client_map[i]->handle == client_handle) {
            client = g_client_map[i];
            break;
        }
    }

    if (!client || client->state == HTTP_STATE_DONE) return;

    lua_event_t lua_event;
    http_event_data_t *http_data;

    switch (event)
    {
        case CM_HTTP_CALLBACK_EVENT_REQ_START_SUCC_IND:
        {
            if (client->request_body_copy && strlen(client->request_body_copy) > 0) {
                cm_httpclient_request_send(client_handle, (const uint8_t*)client->request_body_copy, strlen(client->request_body_copy));
            }
            cm_httpclient_request_end(client_handle);
            break;
        }
        case CM_HTTP_CALLBACK_EVENT_RSP_CONTENT_IND:
        {
            cm_httpclient_callback_rsp_content_param_t *content_param = (cm_httpclient_callback_rsp_content_param_t*)param;

            // First, update total received length
            client->total_len += content_param->current_len;

            // Post progress event
            if (client->on_progress_ref != LUA_NOREF) {
                lua_event_t progress_event;
                progress_event.id = LUA_EVENT_HTTP_PROGRESS;
                http_event_data_t *progress_data = (http_event_data_t*)malloc(sizeof(http_event_data_t));
                progress_data->client_ref = client->self_ref;
                progress_data->received_len = client->total_len;
                progress_data->total_len = content_param->total_len; // total_len from SDK
                progress_event.data.pval = progress_data;
                lua_event_post(&progress_event);
            }

            if (client->file_handle > 0) { // Download to file
                if (content_param->current_len > 0) {
                    cm_fs_write(client->file_handle, (void*)content_param->response_content, content_param->current_len);
                }
            } else { // Accumulate in memory
                if (content_param->current_len > 0) {
                    // Note: total_len is already updated above
                    if (client->total_len > client->buffer_size) {
                        char *new_buffer = realloc(client->response_buffer, client->total_len);
                        if (!new_buffer) {
                            // TODO: Handle realloc failure
                            return;
                        }
                        client->response_buffer = new_buffer;
                        client->buffer_size = client->total_len;
                    }
                    memcpy(client->response_buffer + (client->total_len - content_param->current_len), content_param->response_content, content_param->current_len);
                }
            }
            break;
        }
        case CM_HTTP_CALLBACK_EVENT_RSP_END_IND:
        {
            // For downloads, close the file handle BEFORE posting the event.
            if (client->file_handle > 0) {
                cm_fs_close(client->file_handle);
                client->file_handle = 0;
            }

            client->state = HTTP_STATE_DONE;
            lua_event.id = LUA_EVENT_HTTP_RSP_DONE;
            http_data = (http_event_data_t*)malloc(sizeof(http_event_data_t));
            http_data->client_ref = client->self_ref;
            http_data->status_code = cm_httpclient_get_response_code(client_handle);

            if (client->file_handle > 0) {
                http_data->data = NULL;
                http_data->data_len = 0;
            } else {
                http_data->data = client->response_buffer;
                http_data->data_len = client->total_len;
                client->response_buffer = NULL; // Ownership transferred
            }

            lua_event.data.pval = http_data;
            lua_event_post(&lua_event);
            break;
        }
        case CM_HTTP_CALLBACK_EVENT_ERROR_IND:
        {
            client->state = HTTP_STATE_DONE;
            lua_event.id = LUA_EVENT_HTTP_RSP_ERROR;
            http_data = (http_event_data_t*)malloc(sizeof(http_event_data_t));
            http_data->client_ref = client->self_ref;
            http_data->status_code = *(int*)param;
            http_data->data = NULL;
            http_data->data_len = 0;
            lua_event.data.pval = http_data;
            lua_event_post(&lua_event);
            break;
        }
        default:
            break;
    }
}
